#ifndef _BUOLA_GRAPH_CGRAPH_H_
#define _BUOLA_GRAPH_CGRAPH_H_

#include <buola/buola.h>
#include <buola/iterator/wrap.h>
#include <buola/iterator/subscript.h>
#include <list>
#include <vector>
#include <boost/graph/adjacency_list.hpp>

namespace buola { namespace graph {

namespace detail {
    
    template<typename tVertex,typename tEdge>
    struct MGraphAdaptor
    {
        typedef OConditional<std::is_same<tVertex,UUnused>,boost::no_property,tVertex> TVertexProperty;
        typedef OConditional<std::is_same<tEdge,UUnused>,boost::no_property,tEdge> TEdgeProperty;
        
        typedef boost::adjacency_list<boost::vecS,boost::vecS,boost::undirectedS,TVertexProperty,TEdgeProperty> TAdjacencyList;
    };
    
/*namespace detail*/ }
    
template<typename tVertex=UUnused,typename tEdge=UUnused>
class CGraph : public detail::MGraphAdaptor<tVertex,tEdge>::TAdjacencyList
{
    typedef typename detail::MGraphAdaptor<tVertex,tEdge>::TAdjacencyList TBase;
public:
    CGraph()=default;
    
    template<typename tIt>
    CGraph(const tIt &pB,const tIt &pE)
    {
        for(tIt i=pB;i!=pE;++i)
        {
            add_vertex(*i,*this);
        }
    }
    
    CGraph(const CGraph &pO)=default;
    CGraph &operator=(const CGraph &pRH)=default;

    CGraph(CGraph &&pO)
    {
        this->swap(pO);
    }
    
    CGraph &operator=(CGraph &&pRH)
    {
        this->swap(pRH);
        return *this;
    }

    auto EdgeMap() const -> decltype(boost::get(boost::edge_bundle,*this))
    {
        return boost::get(boost::edge_bundle,*this);
    }

    auto VertexMap() const -> decltype(boost::get(boost::vertex_bundle,*this))
    {
        return boost::get(boost::vertex_bundle,*this);
    }
    
    typename TBase::vertex_descriptor MaxVertexDescriptor() const
    {
        return boost::num_vertices(*this)-1;
    }
    
    using TBase::operator[];
    
    template<typename tRange>
    auto operator[](const tRange &pRange) -> decltype(subscript_range(pRange,*this))
    {
        return subscript_range(pRange,*this);
    }

    template<typename tRange>
    auto operator[](const tRange &pRange) const -> decltype(subscript_range(pRange,*this))
    {
        return subscript_range(pRange,*this);
    }
};

template<typename tVertex,typename tEdge>
CRange<typename CGraph<tVertex,tEdge>::vertex_iterator> vertices(const CGraph<tVertex,tEdge> &pGraph)
{
    return CRange<typename CGraph<tVertex,tEdge>::vertex_iterator>(boost::vertices(pGraph));
}

template<typename tVertex,typename tEdge>
CRange<typename CGraph<tVertex,tEdge>::edge_iterator> edges(const CGraph<tVertex,tEdge> &pGraph)
{
    return CRange<typename CGraph<tVertex,tEdge>::edge_iterator>(boost::edges(pGraph));
}

template<typename tRange,typename tPred>
CGraph<typename tRange::value_type> build(const tRange &pRange,tPred pPred)
{
    CGraph<typename tRange::value_type> lGraph(pRange.begin(),pRange.end());
    auto lEnd=vertices(lGraph).end();
    for(auto i=vertices(lGraph).begin();i!=lEnd;++i)
    {
        for(auto j=vertices(lGraph).begin();j!=i;++j)
        {
            if(pPred(lGraph[*i],lGraph[*j]))
                add_edge(*i,*j,lGraph);
        }
    }

    return lGraph;
}

template<typename tRange,typename tPred,typename tFunc>
auto build(const tRange &pRange,tPred pPred,tFunc pFunc) -> CGraph<typename tRange::value_type,
                                                              ODecay<decltype(pFunc(*pRange.begin(),*pRange.end()))>>
{
    CGraph<typename tRange::value_type,ODecay<decltype(pFunc(*pRange.begin(),*pRange.end()))>> 
                    lGraph(pRange.begin(),pRange.end());
    auto lEnd=vertices(lGraph).end();
    for(auto i=vertices(lGraph).begin();i!=lEnd;++i)
    {
        for(auto j=vertices(lGraph).begin();j!=i;++j)
        {
            if(pPred(lGraph[*i],lGraph[*j]))
                add_edge(*i,*j,pFunc(lGraph[*i],lGraph[*j]),lGraph);
        }
    }

    return lGraph;
}

/*namespace graph*/ } /*namespace buola*/ }

#endif
